#include "./conn_uart.h"
#include "../compat.h"
#include "../rtos/message_queue.h"
#include "../rtos/event_flags.h"
#include "../conn/proto.h"

// PA9 TX DMA1_Channel4
// PA10 RX DMA_Channel5

#define DMA_RECV_BUF_SIZE 64
static uint8_t dmaRecvBuf[DMA_RECV_BUF_SIZE];
static volatile uint32_t dmaRecvIndex = 0;
#define DMA_WRITE_BUF_SIZE 64
static uint8_t dmaWriteBuf[DMA_WRITE_BUF_SIZE];

MESSAGE_QUEUE_INFO(Conn, 10, ByteView);
EVENT_FLAGS_INFO(Conn);

static void write_by_dma(const uint8_t *buf, uint16_t len);

#define DMA_WRITE_FLAG 0x01

void conn_uart_init() {
    Conn_message_queue_create();
    Conn_event_flags_create();
    Conn_event_flags_set(DMA_WRITE_FLAG);
    REG_MODIEF(RCC->APB2ENR, 0, RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN);
    REG_MODIEF(RCC->AHBENR, 0, RCC_AHBENR_DMA1EN);

    REG_MODIEF(GPIOA->CRH, 0xFF0, 0x4B0);

    USART1->CR1 = 0x00;
    USART1->CR2 = 0x00;
    USART1->CR3 = USART_CR3_DMAR | USART_CR3_DMAT;
    USART1->BRR = 8000000 / 9600;
    USART1->CR1 = 0x201C;

    DMA1->IFCR = DMA_IFCR_CGIF5 |
        DMA_IFCR_CHTIF5 |
        DMA_IFCR_CTCIF5 |
        DMA_IFCR_CTEIF5;
    DMA1_Channel5->CCR = 0x00;
    DMA1_Channel5->CMAR = (uint32_t) dmaRecvBuf;
    DMA1_Channel5->CPAR = (uint32_t) &USART1->DR;
    DMA1_Channel5->CNDTR = DMA_RECV_BUF_SIZE;
    DMA1_Channel5->CCR = (0x01 << 7) + (0x01 << 5) + 0x01;

    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}

osStatus_t conn_uart_poll(ByteView *bw, uint32_t timeout) {
    return Conn_message_queue_poll(bw, timeout);
}

void conn_uart_write(const uint8_t *buf, uint16_t len) {
    Conn_event_flags_wait_any(DMA_WRITE_FLAG, osWaitForever);
    write_by_dma(buf, len);
}

void conn_printf(const char *fmt, ...) {
    Conn_event_flags_wait_any(DMA_WRITE_FLAG, osWaitForever);
    va_list ap;
    va_start(ap, fmt);
    uint16_t len = proto_make_printf(dmaWriteBuf, fmt, ap);
    write_by_dma(dmaWriteBuf, len);
}

void conn_write_simple_res(uint8_t seq, uint8_t cmd, uint8_t ec) {
    Conn_event_flags_wait_any(DMA_WRITE_FLAG, osWaitForever);
    uint16_t len = proto_make_simple_res(dmaWriteBuf, seq, cmd, ec);
    write_by_dma(dmaWriteBuf, len);
}

static void write_by_dma(const uint8_t *buf, uint16_t len) {
    DMA1->IFCR = DMA_IFCR_CGIF4 |
        DMA_IFCR_CHTIF4 |
        DMA_IFCR_CTCIF4 |
        DMA_IFCR_CTEIF4;
    DMA1_Channel4->CCR = 0x00;
    DMA1_Channel4->CMAR = (uint32_t) buf;
    DMA1_Channel4->CPAR = (uint32_t) &USART1->DR;
    DMA1_Channel4->CNDTR = len;
    DMA1_Channel4->CCR = (0x01 << 7) + (0x01 << 4) + (0x01 << 1) + 0x01;
}

static void on_recv_idle() {
    uint32_t index = DMA_RECV_BUF_SIZE - DMA1_Channel5->CNDTR;
    if (index == dmaRecvIndex) {
        return;
    }
    if (index > dmaRecvIndex) {
        ByteView bw = {
            .buf = dmaRecvBuf + dmaRecvIndex,
            .len = index - dmaRecvIndex,
        };
        Conn_message_queue_post(&bw);
    } else {
        ByteView bw = {
            .buf = dmaRecvBuf + dmaRecvIndex,
            .len = DMA_RECV_BUF_SIZE - dmaRecvIndex,
        };
        Conn_message_queue_post(&bw);
        if (index > 0) {
            bw.buf = dmaRecvBuf;
            bw.len = index;
            Conn_message_queue_post(&bw);
        }
    }
    dmaRecvIndex = index;
}

void USART1_IRQHandler() {
    if (USART1->SR & USART_SR_IDLE) {
        uint32_t c = USART1->DR;
        on_recv_idle();
    }
}

void DMA1_Channel4_IRQHandler() {
    if (DMA1->ISR & DMA_ISR_TCIF4) {
        DMA1->IFCR = DMA_IFCR_CTCIF4;
        Conn_event_flags_set(DMA_WRITE_FLAG);
    }
}
